#ifndef	__camera_H__
#define __camera_H__

#include "rendVect.hpp"
#include "rendClipper.hpp"

namespace rend
{

class Frustum : public Clipper< 6 >
{
public:

	enum FrustumPlane
	{
		PLANE_LEFT		= 0,
		PLANE_RIGHT,
		PLANE_BOTTOM,
		PLANE_TOP,
		PLANE_NEAR,
		PLANE_FAR,

		PLANE_count,
		PLANE_forceType	= -1UL
	};

	Frustum()
	{
		assert((unsigned) PLANE_count == (unsigned) Clipper<6>::NUM_PLANES);
	}
};


class Camera
{
public:

	Camera();

	// control attributes in world space;
	// note that directional attributes are subject to normalisation -- reading such
	// an attribute always returns the normalised version of what was originally set

	void			setPosition(const vect3& position,
								const bool targetLock = false);
	const vect3&	getPosition() const;

	// target and direction attributes are complementary - changing any one of them will affect the other
	void			setTarget(const vect3& target);
	const vect3&	getTarget() const;

	void			setDirection(const vect3& direction);
	const vect3&	getDirection() const;

	void			setRoll(const vect3& up);
	const vect3&	getRoll() const;

	enum Projection
	{
		PROJECTION_ORTHOGRAPHIC,
		PROJECTION_PERSPECTIVE
	};

	// also resets the projection to the default one for the given type
	void			setProjectionType(const Projection type);
	Projection		getProjectionType() const;

	// projection control:
	// under perspective projection FOV and projection extents are
	// complementary - changing either of them automatically updates the other;
	// under orthographic projection FOV is not applicable, so projection
	// extents remain the only mechansim to control the view width/height;

	// accessor/mutator to the half-angle defining the cameras's horizontal filed of view;
	// valid for perspective camera only!
	void			setFovX(const float angle);
	float			getFovX() const;

	// accessor/mutator to the half-angle defining the cameras's vertical filed of view;
	// valid for perspective camera only!
	void			setFovY(const float angle);
	float			getFovY() const;

	// accessor/mutator to the half-extent of the view's projection along the camera's horizontal
	void			setExtentX(const float extent);
	float			getExtentX() const;

	// accessor/mutator to the half-extent of the view's projection along the camera's vertical
	void			setExtentY(const float extent);
	float			getExtentY() const;

	// distance to near/far clipping planes -- the far one must be greater then the near one;
	// also, under perspective projection either distances must be positive!
	void			setNearClipDistance(const float distance);
	float			getNearClipDistance() const;

	void			setFarClipDistance(const float distance);
	float			getFarClipDistance() const;

	// camera transformation;
	// transform is right-handed; Z points at the viewer, -Z - along the view vector,
	// Y points up, and X points to the right

	// camera-to-world, AKA inverse camera transform
	bool			setWorldTransform(const matx4& matx);			// true if invertible
	const matx4&	getWorldTransform() const;

	// world-to-camera, AKA forward camera transform
	bool			setInverseWorldTransform(const matx4& matx);	// true if invertible
	const matx4&	getInverseWorldTransform() const;

	// projection transformation;
	// must staisfy the condition that NDC space spans [-1, 1] along all axes
	void			setProjection(const matx4& matx);
	const matx4&	getProjection() const;
	const matx4&	getInverseProjection() const;

	// get a snapshot of the current frustum in a specified 'origin' space (world space if 'origin' transform is identity)
	bool			getFrustum(								// true if returned frustum is valid
						Frustum& frustum,					// resulting clipping frustum (may not be valid)
						const matx4& origin) const;			// origin-to-world transform

protected:

	void			updateAttribsFromCamTransform();
	void			updateAttribsFromProjTransform();
	void			updateCamTransformFromAttribs() const;
	void			updateProjTransformFromAttribs() const;

	void			setupDefaultPerspective();
	void			setupDefaultOrthographic();

private:

	Projection		mType;

	mutable matx4	mTran;			// world transform
	mutable matx4	mTranI;			// view transform
	mutable matx4	mProj;			// forward projection transform
	mutable matx4	mProjI;			// inverse projection transform

	mutable bool	mDirtyCam;		// camera transforms need update
	mutable bool	mDirtyProj;		// projection transform needs update
	mutable bool	mInvertible;	// forward/inverse transforms are valid

	vect3			mPos;
	vect3			mForward;
	vect3			mUp;

	vect3			mTarget;

	float			mFovX;			// horizontal FOV for perspective transform, (0, Pi/2)
	float			mFovY;			// vertical FOV for perspective transform, (0, Pi/2)
	float			mProjExtX;		// horizontal extent of the projection plane; plane spans [-ext, ext]
	float			mProjExtY;		// vertical extent of the projection plane; plane spans [-ext, ext]
	float			mClipN;			// distance (along the view vector) to the near clipping plane (< far_plane)
	float			mClipF;			// distance (along the view vector) to the far clipping plane
};

} // namespace rend

#endif // __camera_H__
